package mahjong;

import java.io.Serializable;

/**
 * A single tile within the game.
 * Tiles have unique ID's starting from 0. They also store references to the
 * tiles that are adjacent to it in order to determine whether a tile can be
 * removed.
 */
public class Tile implements Comparable<Tile>, Serializable
{
	/** Unique ID of a tile on the board. Starts at 0. */
	private int id;
	/** Group value. Determines whether two tiles mathch. */
	private int value;
	/** Index within the group (0-3).
	 * Determine the texture of the tile together with the group value.*/
	private int subindex;

	/** The <tt>x<tt> coordinate of the tile in the layout. */
	private int x;
	/** The <tt>y<tt> coordinate of the tile in the layout. */
	private int y;
	/** The <tt>z<tt> coordinate of the tile in the layout. */
	private int z;
	/** Indicates whether the tile is selected by the player. */
	private boolean selected;
	
	/** Indicates whether the tile is selected by the AI. */
	private boolean aiSelected;
	
	
	private int hint = 0;
	
	/** Reference to adjacent tile to the up left. **/
	private Tile upLeft;
	/** Reference to adjacent tile to the up right. **/
	private Tile upRight;
	/** Reference to adjacent tile to the lower right. **/
	private Tile lowerRight;
	/** Reference to adjacent tile to the lower left. **/
	private Tile lowerLeft;
	/** Reference to adjacent tile to the up left at the level above. **/
	private Tile topUpLeft;
	/** Reference to adjacent tile to the up right at the level above. **/
	private Tile topUpRight;
	/** Reference to adjacent tile to the lower right at the level above. **/
	private Tile topLowerRight;
	/** Reference to adjacent tile to the lower left at the level above. **/
	private Tile topLowerLeft;
	
	/** Constant denoting upper left */
	public static final int UL = 0;
	/** Constant denoting upper right */
	public static final int UR = 1;
	/** Constant denoting lower right */
	public static final int LR = 2;
	/** Constant denoting lower left */
	public static final int LL = 3;
	/** Constant denoting upper left on top */
	public static final int TUL = 4;
	/** Constant denoting upper right on top */
	public static final int TUR = 5;
	/** Constant denoting lower right on top */
	public static final int TLR = 6;
	/** Constant denoting lower left on top */
	public static final int TLL = 7;

	/** Removed flag **/
	private boolean isRemoved;
	
	/** Default Tile constructor.
	 */
	public Tile()
	{
		upLeft = null;
		upRight = null;
		lowerLeft = null;
		lowerRight = null;
		topUpLeft = null;
		topUpRight = null;
		topLowerLeft = null;
		topLowerRight = null;
		id = -1;
		x = -1;
		y = -1;
		z = -1;
		value = -1;
		subindex = -1;
		selected = false;
		isRemoved = false;
	}

	/**
	 * Tile constructor.
	 * @param val group value.
	 * @param index sub-index within the group.
	 */
	public Tile(int val,int index)
	{
		this();
		value = val;
		subindex = index;
	}

	/**
	 * Set location in the board. 
	 * @param z1 z-coord.
	 * @param y1 y-coord.
	 * @param x1 x-coord.
	 */
	public void setCoord(int z1,int y1, int x1)
	{
		z = z1;
		y = y1;
		x = x1;
	}

	/** Get the z-coordinate.
	 * @return the z-coord.
	 */
	public int getZ()
	{
		return z;
	}

	/** Get the y-coordinate.
	 * @return the y-coord.
	 */
	public int getY()
	{
		return y;
	}

	/** Get the x-coordinate.
	 * @return the x-coord.
	 */
	public int getX()
	{
		return x;
	}

	/** Set tile ID.
	 * @param i the ID.
	 */
	public void setId(int i)
	{
		id = i;
	}

	/** Get the tile ID.
	 * @return the ID.
	 */
	public int getId()
	{
		return id;
	}

	/** Set tile group.
	 * @param v the group value.
	 */
	public void setValue(int v)
	{
		value = v;
	}

	/** Set tile subindex within a group.
	 * @param s the subindex value.
	 */
	public void setSubindex(int s)
	{
		subindex = s;
	}

	/** Get tile group.
	 * @return the group value.
	 */
	public int getValue()
	{
		return value;
	}

	/** Get tile subindex within a group.
	 * @return the subindex.
	 */
	public int getSubindex()
	{
		return subindex;
	}

	/** Determine if the tile is selected by the player.
	 * @return true if selected, false otherwise.
	 */
	public boolean isSelected()
	{
		return selected;
	}
	
	/**
	 * Determine if the tile is selected by the ai
	 * @return true if selected, false otherwise
	 */
	public boolean isAISelected()
	{
		return aiSelected;
	}
	
	/**
	 * Set the ai selection status of the tile
	 * @param s boolean value to which tile ai selection is set
	 */
	public void aiSelected(boolean s)
	{
		aiSelected = s;
	}

	/** Set the selection status of a tile.
	 * @param selected boolean value to which tile selection is set.
	 */
	public void setSelected(boolean selected)
	{
		this.selected = selected;
	}

	/** Interface method.
	 * @param t The Tile to compare to.
	 * @return -1, 0, or 1. 
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	public int compareTo(Tile t)
	{
		if (value<t.value || (value==t.value && subindex<t.subindex)) {
			return -1;
		}

		if (value>t.value || (value==t.value && subindex>t.subindex)) {
			return 1;
		}

		return 0;
	}

	/** Checks if this tile is a matching tile for the given one. 
	 * @param t Tile to check.
	 * @return true if this tile matches the input, false otherwise. */
	public boolean matches(Tile t)
	{
		return (t.value == value);
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj)
	{
		if (obj instanceof Tile) {
			Tile t = (Tile)obj;
			if (value==t.value && subindex==t.subindex) {
				return true;
			}
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode()
	{
		return x + y << 2 + z << 4 + id << 6;
	}
	
	/**
	 * Sets the adjacent tile at the given position.
	 * @param pos the position of the adjacent tile, one of the position
	 * constant defined above.
	 * @param tile the tile to be set adjacently .
	 */
	public void setAdjacentTile(int pos, Tile tile) 
	{
		switch (pos) {
			case UL:
				upLeft = tile;
				break;
			case UR:
				upRight = tile;
				break;
			case LR:
				lowerRight = tile;
				break;
			case LL:
				lowerLeft = tile;
				break;
			case TUL:
				topUpLeft = tile;
				break;
			case TUR:
				topUpRight = tile;
				break;
			case TLR:
				topLowerRight = tile;
				break;
			case TLL:
				topLowerLeft = tile;
				break;
			default:
				return;
		}
	}
	

	/** Checks if the tile can be selected or removed
	 * @return true if the tile can be selected or removed, false otherwise.
	 */
	public boolean canRemove()
	{
		// Check if the tile is removed
		if (isRemoved) return false;
		
		// Check if any adjacent tiles are on top
		for (int i = TUL; i < TLL + 1; i++) {
			if (getAdjacentTile(i) != null && !getAdjacentTile(i).isRemoved())
				return false;
		}
		
		
		boolean leftopen = true;
		boolean rightopen = true;
		
		// check adjacent tiles to the left
		if ((getAdjacentTile(UL) != null && !getAdjacentTile(UL).isRemoved())
				|| (getAdjacentTile(LL) != null && !getAdjacentTile(LL).isRemoved()))
			leftopen = false;
		// check adjacent tiles to the right
		if ((getAdjacentTile(UR) != null && !getAdjacentTile(UR).isRemoved())
				|| (getAdjacentTile(LR) != null && !getAdjacentTile(LR).isRemoved()))
			rightopen = false;
		
		if (!leftopen && !rightopen) return false;
		
		return true;
	}
	
	/**
	 * Gets the adjacent tile at the given position.
	 * @param pos the position of the adjacent tile, one of the position
	 * constant defined above.
	 */
	public Tile getAdjacentTile(int pos)
	{
		switch(pos) {
			case UL: return upLeft;
			case UR: return upRight;
			case LR: return lowerRight;
			case LL: return lowerLeft;
			case TUL: return topUpLeft;
			case TUR: return topUpRight;
			case TLR: return topLowerRight;
			case TLL: return topLowerLeft;
			default: return null;
		}
	}
	
	/** Determines if a tile is removed from the board.
	 * @return true if the tile is removed, false otherwise */
	public boolean isRemoved()
	{
		return isRemoved;
	}
	
	/** Sets the tile as not removed */
	public void addToPlay()
	{
		isRemoved = false;
	}

	/** Sets the tile as removed */
	public void removeFromPlay()
	{
		isRemoved = true;
	}

	public void setY(int y1)
	{
		y = y1;
	}

	public String toString()
	{
		return "Tile [id = " + id + " x = " + x + " y = " + y + " z = " + z
			+ " group = " + value + " subindex = " + subindex + "]";
	}

	public void setHint(int h)
	{
		hint = h;
	}

	public int returnHint()
	{
		return hint;
	}

	public Tile getUpLeft() {
		return upLeft;
	}

	public Tile getUpRight() {
		return upRight;
	}

	public Tile getLowerRight() {
		return lowerRight;
	}

	public Tile getLowerLeft() {
		return lowerLeft;
	}

	public boolean isTopFree(){
		if(topUpRight!= null)
		{
			if(!topUpRight.isRemoved()) return false;
		}
		if(topUpLeft!= null)
		{
			if(!topUpLeft.isRemoved()) return false;
		}
		if(topLowerRight!= null)
		{
			if(!topLowerRight.isRemoved()) return false;
		}
		if(topLowerLeft!= null)
		{
			if(!topLowerLeft.isRemoved()) return false;
		}
		return true;	
	}
	public boolean isVisible(){
		//if tile covering is null or is removed for any corner mark as visible.
		if(topUpRight!= null)
		{
			if(topUpRight.isRemoved()) return true;
		}else{
			return true;
		}
		if(topUpLeft!= null)
		{
			if(topUpLeft.isRemoved()) return true;
		}else{
			return true;
		}
		if(topLowerRight!= null)
		{
			if(topLowerRight.isRemoved()) return true;
		}else{
			return true;
		}
		if(topLowerLeft!= null)
		{
			if(topLowerLeft.isRemoved()) return true;
		}else{
			return true;
		}
		return false;	
	}
	
}
